
using BEPUphysics;
using BEPUphysics.Collidables;
using BEPUphysics.Collidables.MobileCollidables;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUphysics.DataStructures;
using BEPUphysics.Entities;
using BEPUphysics.Entities.Prefabs;
using BEPUphysics.MathExtensions;
using BEPUphysics.NarrowPhaseSystems.Pairs;
using System.ComponentModel;
using LevelEditor.BepuTools;
using LevelEditor.EditorSupport;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Nine;
using Nine.Graphics;
using Nine.Graphics.Effects;
using PerformanceUtility.GameDebugTools;
using PerformanceUtility.ShapeRendering;
using BEPUphysics.CollisionShapes;

// TODO import to actual game, loading models and bboxes if specified
// TODO create bounding spheres/boxes that can be toggled on/off that show accessible areas using BEPU

namespace LevelEditor
{
    [Category("Graphics")]
    [DisplayName("Shadow Mapping")]
    [Description("This sample demenstrates how to draw shadows using shadow mapping.")]
    public class LevelEditorGame : Microsoft.Xna.Framework.Game
    {
        SpriteBatch spriteBatch;

        ModelBatch modelBatch;

        DepthEffect depth;
        ShadowMap shadowMap;

        Input input;
        FrameRate fr;

        DebugSystem debugSystem;

        public LevelEditorGame()
        {
            GraphicsDeviceManager graphics = new GraphicsDeviceManager(this);

            graphics.SynchronizeWithVerticalRetrace = false;
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;

            Content.RootDirectory = "Content";
            EditorComponents.Content = Content;
            EditorComponents.Game = this;

            IsMouseVisible = true;
            IsFixedTimeStep = false;
            fr = new FrameRate(this, "Fonts/Consolas");
            Components.Add(fr);
            Components.Add(new InputComponent(Window.Handle));
        }

        protected override void Initialize()
        {
            // initialize the debug system with the game and the name of the font 
            // we want to use for the debugging
            debugSystem = DebugSystem.Initialize(this, "Fonts/debugFont");

            // register a new command that lets us exit
            debugSystem.DebugCommandUI.RegisterCommand(
                "exit",                 // Name of command
                "Exit application",     // Description of command
                DebugFunctions.ExitCommand);
            debugSystem.DebugCommandUI.RegisterCommand(
                "quit",                 // Name of command
                "Exit application",     // Description of command
                DebugFunctions.ExitCommand);

            // register command for loading a model that already exists in the content
            debugSystem.DebugCommandUI.RegisterCommand(
                "load",                 // Name of command
                "Load a model(s) from the compiled content",     // Description of command
                DebugFunctions.LoadModel);

            // register command for listing all existing models
            debugSystem.DebugCommandUI.RegisterCommand(
                "list",                 // Name of command
                "List all models, can filter by typing start of model name",     // Description of command
                DebugFunctions.ListModels);

            // register command for selecting specific models
            debugSystem.DebugCommandUI.RegisterCommand(
                "select",                 // Name of command
                "Select all models starting with argument",     // Description of command
                DebugFunctions.SelectModels);

            // register command for deleting specific models
            debugSystem.DebugCommandUI.RegisterCommand(
                "delete",                 // Name of command
                "Delete all models starting with argument",     // Description of command
                DebugFunctions.DeleteModels);

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            modelBatch = new ModelBatch(GraphicsDevice);

            // Create a model viewer InputComponents.Camera to help us visualize the scene
            EditorComponents.Camera = new TopDownEditorCamera(GraphicsDevice, 2000, MathHelper.PiOver2 + MathHelper.PiOver4);
            EditorComponents.Camera.Radius = EditorComponents.Camera.MaxRadius / 2;
            EditorComponents.Camera.Sensitivity = 2.0f;

            EditorComponents.LoadLevel(ref LevelObjects.StillModels, ref LevelObjects.EventBoxes, ref LevelObjects.EventSpheres, Content, LevelObjects.LightView, LevelObjects.LightProjection);

            //LevelComponents.AnimatedModels.Add(new EditorAnimModels("dude", Content.Load<Model>("dude"), Vector3.Zero, new Vector3(0.3f, 0.3f, 0.3f), new Vector3(MathHelper.PiOver2, 0, 0)));

            // Create shadow map related effects, depth is used to generate shadow maps, shadow is used to draw a shadow receiver with a shadow map.
            shadowMap = new ShadowMap(GraphicsDevice, 2048);
            depth = new DepthEffect(GraphicsDevice);

            //for (int i = 0; i < LevelObjects.AnimatedModels.Count; i++)
            //{
            //    EditorComponents.LoadShadowEffect(LevelObjects.AnimatedModels[i].model, "Effects/ShadowNormalSkinnedEffect", Content, LevelObjects.LightView, LevelObjects.LightProjection);
            //}

            // Enable shadowmap bluring, this will produce smoothed shadow edge. You can increase the shadow quality by using a larger shadowmap resolution (like 1024 or 2048), and increase the blur samples.
            shadowMap.BlurEnabled = true;

            // Input
            input = new Input();

            // BEPU physics initialization
            EditorComponents.space = new Space();
            EditorComponents.space.ForceUpdater.Gravity = new Vector3(0, -9.81f, 0);
            for (int i = 0; i < LevelObjects.StillModels.Count; i++)
            {
                // Only generate mesh if we want to
                if (!LevelObjects.StillModels[i].GenerateCollisionMesh)
                    continue;

                Vector3[] staticTriangleVertices;
                int[] staticTriangleIndices;

                TriangleMesh.GetVerticesAndIndicesFromModel(LevelObjects.StillModels[i].NineModel, out staticTriangleVertices, out staticTriangleIndices);

                var staticMesh = new StaticMesh(staticTriangleVertices, staticTriangleIndices, new AffineTransform(LevelObjects.StillModels[i].Scale, Quaternion.CreateFromRotationMatrix(Matrix.CreateRotationX(LevelObjects.StillModels[i].Rotation.X) * Matrix.CreateRotationY(LevelObjects.StillModels[i].Rotation.Y) * Matrix.CreateRotationZ(LevelObjects.StillModels[i].Rotation.Z)), LevelObjects.StillModels[i].Position));
                staticMesh.Sidedness = TriangleSidedness.Counterclockwise;

                staticMesh.Tag = LevelObjects.StillModels[i].ID;

                LevelObjects.StillModels[i].Mesh = staticMesh;

                // Add to the space
                EditorComponents.space.Add(LevelObjects.StillModels[i].Mesh);
            }

            //This 1x1x1 cube model will represent the box entities in the space.
            EditorComponents.cube = Content.Load<Model>("Models/_Required/cube");

            // Initialize our renderer
            DebugShapeRenderer.Initialize(GraphicsDevice);
        }

        protected override void Draw(GameTime gameTime)
        {
            debugSystem.TimeRuler.StartFrame();
            debugSystem.TimeRuler.BeginMark("Draw", Color.Yellow);

            // Clear base, not necessary, just keeps background color from flickering in editor
            GraphicsDevice.Clear(Color.DarkSlateGray);

            // Update model animation.
            for (int i = 0; i < LevelObjects.AnimatedModels.Count; i++)
            {
                LevelObjects.AnimatedModels[i].animation.Update(gameTime);
            }

            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            if (LevelObjects.DrawModels)
            {
                // We need to draw the shadow casters on to a render target.
                if (LevelObjects.StillModels.Count > 0 || LevelObjects.AnimatedModels.Count > 0)
                {
                    shadowMap.Begin();
                    {
                        // Clear everything to white. This is required.
                        GraphicsDevice.Clear(Color.White);

                        GraphicsDevice.BlendState = BlendState.Opaque;

                        // Draw shadow casters using depth effect with the matrices set to light view and projection.
                        modelBatch.Begin(LevelObjects.LightView, LevelObjects.LightProjection);

                        for (int i = 0; i < LevelObjects.StillModels.Count; i++)
                        {
                            if (!LevelObjects.StillModels[i].DisplayShadow || !LevelObjects.StillModels[i].DisplayModel)
                                continue;

                            modelBatch.Draw(LevelObjects.StillModels[i].NineModel, LevelObjects.StillModels[i].WorldMatrix, depth);
                        }
                        for (int i = 0; i < LevelObjects.AnimatedModels.Count; i++)
                        {
                            modelBatch.DrawSkinned(LevelObjects.AnimatedModels[i].model, Matrix.CreateScale(LevelObjects.AnimatedModels[i].scale) * Matrix.CreateRotationX(LevelObjects.AnimatedModels[i].rotation.X) * Matrix.CreateRotationY(LevelObjects.AnimatedModels[i].rotation.Y) * Matrix.CreateRotationZ(LevelObjects.AnimatedModels[i].rotation.Z) * Matrix.CreateTranslation(LevelObjects.AnimatedModels[i].position), LevelObjects.AnimatedModels[i].animation.GetBoneTransforms(), depth);
                        }

                        modelBatch.End();
                    }
                    // We got a shadow map rendered.
                    shadowMap.End();
                }

                // Set textures
                for (int i = 0; i < LevelObjects.StillModels.Count; i++)
                {
                    foreach (IEffectTexture effect in LevelObjects.StillModels[i].NineModel.GetEffects())
                        effect.SetTexture(TextureNames.ShadowMap, shadowMap.Texture);
                }
                for (int i = 0; i < LevelObjects.AnimatedModels.Count; i++)
                {
                    foreach (IEffectTexture effect in LevelObjects.AnimatedModels[i].model.GetEffects())
                        effect.SetTexture(TextureNames.ShadowMap, shadowMap.Texture);
                }

                // Now we begin drawing real scene.
                GraphicsDevice.Clear(Color.DarkSlateGray);

                // Draw bounding boxes for selected objects
                if (LevelObjects.StillModels.Count > 0 || LevelObjects.AnimatedModels.Count > 0)
                {
                    // Draw all shadow receivers with the shadow effect
                    modelBatch.Begin(ModelSortMode.Effect, EditorComponents.Camera.View, EditorComponents.Camera.Projection);
                    {
                        ////modelBatch.Draw(skyBox, Matrix.Identity, skyBoxEffect);
                        for (int i = 0; i < LevelObjects.StillModels.Count; i++)
                        {
                            if (!LevelObjects.StillModels[i].DisplayModel)
                                continue;

                            modelBatch.Draw(LevelObjects.StillModels[i].NineModel, LevelObjects.StillModels[i].WorldMatrix, null);
                        }
                        for (int i = 0; i < LevelObjects.AnimatedModels.Count; i++)
                        {
                            modelBatch.DrawSkinned(LevelObjects.AnimatedModels[i].model, Matrix.CreateScale(LevelObjects.AnimatedModels[i].scale) * Matrix.CreateRotationX(LevelObjects.AnimatedModels[i].rotation.X) * Matrix.CreateRotationY(LevelObjects.AnimatedModels[i].rotation.Y) * Matrix.CreateRotationZ(LevelObjects.AnimatedModels[i].rotation.Z) * Matrix.CreateTranslation(LevelObjects.AnimatedModels[i].position), LevelObjects.AnimatedModels[i].animation.GetBoneTransforms(), null);
                        }


                    }
                    modelBatch.End();
                }
            }

            // Draw bounding box if creating event box
            if (LevelObjects.CreateMin != LevelObjects.CreateMax)
            {
                DebugShapeRenderer.AddBoundingBox(new BoundingBox(LevelObjects.CreateMin, LevelObjects.CreateMax), Color.SkyBlue);
            }
            // Draw bounding sphere if creating event sphere
            if (LevelObjects.CreateRadius > 0.0f)
            {
                DebugShapeRenderer.AddBoundingSphere(new BoundingSphere(LevelObjects.CreateCenter, LevelObjects.CreateRadius), Color.SkyBlue);
            }
            // Draw existing event boxes/spheres
            if (LevelObjects.DrawEvents)
            {
                for (int i = 0; i < LevelObjects.EventBoxes.Count; i++)
                {
                    DebugShapeRenderer.AddBoundingBox(LevelObjects.EventBoxes[i], Color.CornflowerBlue);
                }
                for (int i = 0; i < LevelObjects.EventSpheres.Count; i++)
                {
                    DebugShapeRenderer.AddBoundingSphere(LevelObjects.EventSpheres[i], Color.CornflowerBlue);
                }
            }

            // Draw bounding objects
            for (int i = 0; i < LevelObjects.SelectedModels.Count; i++)
            {
                // Draw selected index in different color
                if (LevelObjects.SelectedModels[i].ID == LevelObjects.SelectedModels[LevelObjects.SelectedModelIndex].ID)
                {
                    DebugShapeRenderer.AddBoundingBox(LevelObjects.SelectedModels[i].Box, Color.HotPink);
                }
                else
                {
                    DebugShapeRenderer.AddBoundingBox(LevelObjects.SelectedModels[i].Box, Color.White);
                }
            }
            DebugShapeRenderer.Draw(gameTime, EditorComponents.Camera.View, EditorComponents.Camera.Projection);


            base.Draw(gameTime);


            // Draw selected item stuff
            spriteBatch.Begin();
            if (LevelObjects.SelectedModels.Count == 0)
            {
                spriteBatch.DrawString(fr.Font, "No model selected...", new Vector2(0, GraphicsDevice.Viewport.Height - fr.Font.LineSpacing), Color.White);
            }
            else
            {
                spriteBatch.DrawString(fr.Font, LevelObjects.SelectedModels[LevelObjects.SelectedModelIndex].ID + " Graphics: " + LevelObjects.StillModels[LevelObjects.SelectedModelIndex].DisplayModel.ToString() + " Bounding: " + LevelObjects.SelectedModels[LevelObjects.SelectedModelIndex].GenerateCollisionMesh.ToString() + " Shadow: " + LevelObjects.SelectedModels[LevelObjects.SelectedModelIndex].DisplayShadow + " \nP:" + LevelObjects.SelectedModels[LevelObjects.SelectedModelIndex].Position.ToString() + " R:" + LevelObjects.SelectedModels[LevelObjects.SelectedModelIndex].Rotation.ToString() + " S:" + LevelObjects.SelectedModels[LevelObjects.SelectedModelIndex].Scale.ToString(), new Vector2(0, GraphicsDevice.Viewport.Height - fr.Font.LineSpacing * 2), Color.White);
            }
            switch (LevelObjects.MoveMode)
            {
                case ModelMovementMode.Rotate:
                    spriteBatch.DrawString(fr.Font, "Model Mode: " + LevelObjects.MoveMode.ToString() + " (" + LevelObjects.SelectedModels.Count.ToString() + " selected, " +InputComponents. RotateSpeed.ToString() + " increment)", new Vector2(0, GraphicsDevice.Viewport.Height - (fr.Font.LineSpacing * 3)), Color.White); break;
                case ModelMovementMode.Scale:
                    spriteBatch.DrawString(fr.Font, "Model Mode: " + LevelObjects.MoveMode.ToString() + " (" + LevelObjects.SelectedModels.Count.ToString() + " selected, " + InputComponents.ScaleSpeed.ToString() + " increment)", new Vector2(0, GraphicsDevice.Viewport.Height - (fr.Font.LineSpacing * 3)), Color.White); break;
                case ModelMovementMode.Translate:
                    spriteBatch.DrawString(fr.Font, "Model Mode: " + LevelObjects.MoveMode.ToString() + " (" + LevelObjects.SelectedModels.Count.ToString() + " selected, " + InputComponents.TranslateSpeed.ToString() + " increment)", new Vector2(0, GraphicsDevice.Viewport.Height - (fr.Font.LineSpacing * 3)), Color.White); break;
                case ModelMovementMode.PlaceCollisionBox:
                case ModelMovementMode.PlaceCollisionSphere:
                    spriteBatch.DrawString(fr.Font, "Model Mode: " + LevelObjects.MoveMode.ToString(), new Vector2(0, GraphicsDevice.Viewport.Height - (fr.Font.LineSpacing * 3)), Color.White); break;
            }
            spriteBatch.End();


            debugSystem.TimeRuler.EndMark("Draw");
        }

        protected override void Update(GameTime gameTime)
        {
            debugSystem.TimeRuler.StartFrame();
            debugSystem.TimeRuler.BeginMark("Update", Color.Blue);
            base.Update(gameTime);
            if (DebugSystem.Instance.DebugCommandUI.Focused)
            {
                debugSystem.TimeRuler.EndMark("Update");
                return;
            }

            if (InputComponents.currentMouse.LeftButton == ButtonState.Pressed &&
                InputComponents.previousMouse.LeftButton == ButtonState.Released &&
                LevelObjects.MoveMode != ModelMovementMode.PlaceCollisionBox &&
                LevelObjects.MoveMode != ModelMovementMode.PlaceCollisionSphere)
            {
                Ray ray = EditorComponents.Game.GraphicsDevice.Viewport.CreatePickRay(Mouse.GetState().X, Mouse.GetState().Y, EditorComponents.Camera.View, EditorComponents.Camera.Projection);
                Plane p = new Plane(Vector3.UnitY, 0.0f);

                float? distToPlane = ray.Intersects(p);

                if (distToPlane != null)
                {
                    // Variables
                    Vector3 dropPos = new Vector3(ray.Position.X + ray.Direction.X * distToPlane.Value, 34, ray.Position.Z + ray.Direction.Z * distToPlane.Value);
                    Vector3[] staticTriangleVertices;
                    int[] staticTriangleIndices;
                    TriangleMesh.GetVerticesAndIndicesFromModel(LevelObjects.StillModels[0].XnaModel, out staticTriangleVertices, out staticTriangleIndices);

                    // Scaling test. NOTE: check entity model drawing for visual scaling
                    for (int i = 0; i < staticTriangleVertices.Length; i++)
                    {
                        staticTriangleVertices[i].X *= 2;
                        staticTriangleVertices[i].Y *= 2;
                        staticTriangleVertices[i].Z *= 2;
                    }

                    // Drop first item
                    var convexHull = new ConvexHull(dropPos, staticTriangleVertices, 10);
                    Vector3 center;
                    var convexHullShape = new ConvexHullShape(staticTriangleVertices, out center);
                    Vector3 difference = Vector3.Zero - center;

                    EditorComponents.space.Add(convexHull);
                    EntityModel model = new EntityModel(convexHull, LevelObjects.StillModels[0].XnaModel, difference, Matrix.Identity, this);
                    Components.Add(model);
                    convexHull.Tag = model;

                    // Drop second item
                    Box toAdd2 = new Box(dropPos, 1, 1, 1, 1);
                    toAdd2.LinearVelocity = Vector3.Normalize(new Vector3(1, 0, 0));
                    EditorComponents.space.Add(toAdd2);
                    EntityModel model2 = new EntityModel(toAdd2, EditorComponents.cube, Vector3.Zero, Matrix.Identity, this);
                    Components.Add(model2);
                    toAdd2.Tag = model2;
                }
            }

            InputComponents.HandleInput(gameTime);

            // Update with time step restrictions to adjust to framerate changes.
            EditorComponents.space.Update((float)gameTime.ElapsedGameTime.TotalSeconds * 3);

            debugSystem.TimeRuler.EndMark("Update");
        }
    }
}
